#include "types.h"

#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uacce.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include <linux/atomic.h>

struct type_device {
    struct cdev cdev;
    struct list_head tasks;
    dev_t dev;
    struct semaphore sem;
};

struct priority_task {
    struct list_head list;
    int id;
    int priority;
};

struct class *type_class;
dev_t type_devno;
struct type_device *type_device;
atomic_t task_id_counter = ATOMIC_INIT(0);

static int schedule_task(struct list_head *tasks, int priority)
{
    pr_info("Scheduling task with priority %d\n", priority);
    struct priority_task *new_task = kmalloc(sizeof(struct priority_task), GFP_KERNEL);
    if (!new_task) {
        return -ENOMEM;
    }
    new_task->id = atomic_inc_return(&task_id_counter);
    new_task->priority = priority;

    // Insert task into list sorted by priority in descending order
    struct priority_task *task;
    struct list_head *pos;
    list_for_each(pos, tasks) {
        task = list_entry(pos, struct priority_task, list);
        if (new_task->priority > task->priority) {
            list_add_tail(&new_task->list, pos);
            break;
        }
    }
    if (pos == &type_device->tasks) {
        list_add_tail(&new_task->list, &type_device->tasks);
    }

    pr_info("Task scheduled: ID=%d, Priority=%d\n", new_task->id, new_task->priority);
    return new_task->id;
}

static void clean_tasks(struct list_head *tasks)
{
    struct priority_task *task, *tmp;
    list_for_each_entry_safe(task, tmp, tasks, list) {
        pr_info("Cleaning task: ID=%d, Priority=%d\n", task->id, task->priority);
        list_del(&task->list);
        kfree(task);
    }
}

static int type_open(struct inode *inode, struct file *file)
{
    pr_info("Type device opened\n");
    struct cdev *cdev = inode->i_cdev;
    struct type_device *tdev = container_of(cdev, struct type_device, cdev);

    file->private_data = tdev;
    return 0;
}

static ssize_t type_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
    pr_info("Type read %zu bytes from position %lld\n", count, *offset);
    struct type_device *tdev = file->private_data;
    struct priority_task *task;
    ssize_t total_len = 0;

    if (*offset != 0) {
        return 0;
    }

    if (down_interruptible(&tdev->sem))
        return -ERESTARTSYS;

    char kbuf[128] = {0};
    list_for_each_entry(task, &tdev->tasks, list) {
        pr_info("Reading task: ID=%d, Priority=%d\n", task->id, task->priority);
        int len = snprintf(kbuf, sizeof(kbuf), "Task ID: %d, Priority: %d\n", task->id, task->priority);
        if (len < 0) {
            total_len = -EFAULT;
            break;
        }
        if (len > count) {
            len = count;
        }
        if (copy_to_user(buf + total_len, kbuf, len)) {
            total_len = -EFAULT;
            break;
        }
        count -= len;
        *offset += len;
        total_len += len;
        if (count == 0) {
            break;
        }
    }

    up(&tdev->sem);

    pr_info("Type: Total bytes read %zu\n", total_len);
    return total_len;
}

static ssize_t type_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
    pr_info("Type write %zu bytes to position %lld\n", count, *offset);
    struct type_device *tdev = file->private_data;
    char kbuf[32];
    int priority;

    if (*offset != 0) {
        pr_err("Type write: Offset beyond data size\n");
        return -EINVAL;
    }

    if (count >= sizeof(kbuf)) {
        pr_err("Type write: Input too large\n");
        return -EINVAL;
    }
    if (copy_from_user(kbuf, buf, count)) {
        return -EFAULT;
    }
    kbuf[count] = '\0';
    priority = simple_strtol(kbuf, NULL, 10);

    if (down_interruptible(&type_device->sem)) {
        return -ERESTARTSYS;
    }

    if (schedule_task(&tdev->tasks, priority) < 0) {
        up(&type_device->sem);
        return -ENOMEM;
    }

    up(&type_device->sem);
    *offset += count;

    return count;
}

static int type_release(struct inode *inode, struct file *file)
{
    pr_info("Type device closed\n");
    return 0;
}

static const struct file_operations type_fops = {
    .owner = THIS_MODULE,
    .open = type_open,
    .read = type_read,
    .write = type_write,
    .release = type_release,
};

static int __init type_init(void)
{
    pr_info("Type module initialized\n");
    int result;
    result = alloc_chrdev_region(&type_devno, 0, 1, MY_TYPE_NAME);
    if (result < 0) {
        pr_err("Failed to allocate char device region\n");
        return result;
    }
    pr_info("Allocated char device region: %d:%d\n",
            MAJOR(type_devno), MINOR(type_devno));

    type_class = class_create(MY_TYPE_CLASS_NAME);
    if (IS_ERR(type_class)) {
        pr_err("Failed to create class\n");
        unregister_chrdev_region(type_devno, 1);
        return PTR_ERR(type_class);
    }

    type_device = kzalloc(sizeof(struct type_device), GFP_KERNEL);
    if (!type_device) {
        pr_err("Failed to allocate type device\n");
        class_destroy(type_class);
        unregister_chrdev_region(type_devno, 1);
        return -ENOMEM;
    }
    cdev_init(&type_device->cdev, &type_fops);
    type_device->cdev.owner = THIS_MODULE;
    type_device->dev = type_devno;
    INIT_LIST_HEAD(&type_device->tasks);
    sema_init(&type_device->sem, 1);

    pr_info("Scheduling initial tasks\n");
    for (int i = 0; i < 5; i++) {
        schedule_task(&type_device->tasks, i * 10);
    }

    result = cdev_add(&type_device->cdev, type_devno, 1);
    if (result) {
        pr_err("Failed to add cdev\n");
        kfree(type_device);
        class_destroy(type_class);
        unregister_chrdev_region(type_devno, 1);
        return result;
    }

    device_create(type_class, NULL, type_devno, NULL, MY_TYPE_NAME);
    pr_info("Type device created successfully\n");
    return 0;
}
module_init(type_init);

static void __exit type_exit(void)
{
    device_destroy(type_class, type_devno);
    cdev_del(&type_device->cdev);
    clean_tasks(&type_device->tasks);
    kfree(type_device);
    class_destroy(type_class);
    unregister_chrdev_region(type_devno, 1);
    pr_info("Type module exited\n");
}
module_exit(type_exit);

MODULE_LICENSE("GPL");